﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Collections;
using System.Xml.Xsl;

namespace Ecopsy.DataTier
{
    public enum EntityState {Updated,Deleted,Loaded,Append} ; 

    public abstract class Entity : ReflectionItem,ISerializableItem,IEntity  
    {
        protected RW_Engine _RW_Engine = null;
                
        private string _HashSign = string.Empty;
        /// <summary>
        /// список префиксов и свойств - не подлежащих серилизации
        /// инициируется в конструкторе 
        /// </summary>
        protected  List <string>  _NonSerializablePropPref =new List <string>  { "_Def", "Def_", "XSLT_Catalog", "XSLT_File", "DefKeyValue", "SaveProcedure", "DeleteProcedure", "LoadProcedure", "Parent", "ImAsXML", "FileName"};

        #region Property 
        
        
        /// <summary>
        /// Состояние объекта.         
        /// </summary>
        [EntityNonSerialized]
        public EntityState State 
        { get 
            {
                string s = GetHash();
                if (String.IsNullOrEmpty(_HashSign)) return EntityState.Append;
                if (_HashSign.Equals(s)) return EntityState.Loaded;
                if (_HashSign.ToString().Equals(EntityState.Deleted.ToString())) return EntityState.Deleted;
                return EntityState.Updated;

            }
            
        }
       
        #endregion

        #region Protected
        protected string GetHash()
        {
            return ToXml(false);
        }

        public  void SetHash()
        {
            _HashSign = GetHash();
        }
               
        #endregion


        /// <summary>
        /// Конструктор
        /// </summary>
        protected   Entity()
        {
            PropertyInfo[] MyFields = this.GetType().GetProperties();
            foreach (PropertyInfo inf in MyFields)
            {
                if (inf.IsDefined(typeof(EntityNonSerializedAttribute), true)) _NonSerializablePropPref.Add(inf.Name); // исключить из серелизации        
            }
            

            Init();
        }

        #region Static
        /// <summary>
        /// Создает бизнес объект и десирилизует его из строки XML
        /// </summary>
        /// <param name="XmlString">      /// 
        /// </param>        
        /// <returns></returns>
        public static Entity GetEntity(string XmlString)
        {
            try
            {
                //считываем xml
                XmlDocument d = new XmlDocument();
                d.LoadXml(XmlString);
                // запускаем рекурсию по структуре объекта
                Entity NewObj = (Entity)ReflectionItem.CreateInstance(d.FirstChild);
                NewObj.ReadXml(d.FirstChild);

                return NewObj;
            }
            catch (Exception ex)
            {
                throw new Exception("Не могу десерилизовать объект", ex);
            }
        }

        /// <summary>
        /// Создает бизнес объект и десирилизует его из строки XML
        /// </summary>
        /// <param name="XmlString">      /// 
        /// </param>
        /// <param name="InstatType">
        /// Тип объекта который будет создан. Тип должен поддерживать  ISerializableItem 
        /// </param>
        /// <returns></returns>
        public static Entity GetEntity(XmlReader reader)
        {
            XmlDocument d;
            try
            {
                //считываем xml
                d = new XmlDocument();
                d.Load(reader);
            }
            catch (Exception ex)
            {
                throw new Exception("Ошибка чтения XML", ex);
            }

            try
            {
                // запускаем рекурсию по структуре объекта
                Entity NewObj = (Entity)ReflectionItem.CreateInstance(d.FirstChild);
                NewObj.ReadXml(d.FirstChild);
                return NewObj;
            }
            catch (Exception ex)
            {
                throw new Exception("Не могу создать объект ", ex);
            }

        }

        /// <summary>
        /// Серилизует объект в строку Если указано Full- выдает полный граф включая элементы отвечающие интерфейсу ISqlObject
        /// 
        /// </summary>
        /// <returns>Строка XML</returns>
        public string ToXml(bool Full)
        {
            try
            {
                StringBuilder sb;
                MemoryStream ms = new MemoryStream();
                sb = new StringBuilder();
                StringWriter sw = new StringWriter(sb, System.Globalization.CultureInfo.InvariantCulture);
                XmlTextWriter w = new XmlTextWriter(sw);
                w.Formatting = Formatting.Indented;
                w.Indentation = 3;

                //this.WriteXml(w, ToString(), Full);
                this.WriteXml(w, TagName, Full);                
                w.Close();
                return sb.ToString();
            }
            catch (Exception ex)
            {
                throw new Exception("Не могу cерилизовать объект", ex);
            }

        }
        #endregion

        /// <summary>
        /// В отличие от стандартного дает сокращенное именование типа.
        /// Используется при серилизации - не перекрывать!
        /// </summary>
        public  sealed  override string ToString()
        {
            return this.GetType().Name;
        }

        #region Virtual
        /// <summary>
        /// Виртуальный метод - вызывается в конструкторе - фактически событие OnInit для наследников.        
        /// </summary>
        public virtual void Init()
        {
        
        }

        #endregion
           
        #region ISerializableItem Members

        /// <summary>
        /// Вернуть XML схему объекта
        /// </summary>
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Втуренний инструмент. Определяет поддерживает и объект ISerializableItem.
        /// </summary>
        /// <remarks>Используется при серилизации/десерилизации</remarks>
        private bool PropertyIsSerializableItem(Type PropType)
        {
            if (null == PropType) return false;

            Type[] ArrType = PropType.GetInterfaces();
            return ImSupprotType(ArrType, typeof(ISerializableItem));
        }

        /// <summary>
        /// Вычислить - входит ли свойство  в заданный массив свойств.
        /// </summary>
        /// <param name="FullList"></param>
        /// <param name="SupportType"></param>
        /// <returns></returns>
        protected  bool ImSupprotType(Type[] FullList, Type SupportType)
        {
            try
            {
                foreach (Type t in FullList)
                {
                    if (t.Equals(SupportType)) return true;
                }
                return false;
            }
            catch
            {
                return false;
            }

        }

        /// <summary>
        /// Вернуть ноду по имени.
        /// </summary>
        private XmlNode GetNodeByName(XmlNode Node, string NodeName)
        {
            try
            {
                foreach (XmlNode item in Node.ChildNodes)
                {
                    if (item.Name.Equals(NodeName)) return item;
                }
                return null;
            }
            catch
            {
                throw;
            }
        }


        /// <summary>
        /// Десерилизовать объект из  XML ноды.
        /// </summary>
        /// <remarks>Алоритм. Пройтись по полям объекта - инициализировать поля объекта значеними одноименных полей ноды.</remarks>
        public  virtual void ReadXml(System.Xml.XmlNode Node)
        {
            string s=string.Empty  ;
            Entity NewObj =null;
            if (null == Node) return;
            try
            {
                //string sAssmblyName =string .Empty ;
                //string sItemAssemblyType = string .Empty;
                //Type MyType=null;
                
                //if (Node.Attributes[SerilizeContext.AssemblyPref] != null)
                //{
                //    sAssmblyName = Node.Attributes[SerilizeContext.AssemblyPref].Value;
                //    sItemAssemblyType = Node.Attributes[SerilizeContext.TypePref].Value;
                //    MyType = ReflectionItem.MyGetType(sAssmblyName, sItemAssemblyType);
                //}
                
                //if (null == MyType) 
                //    MyType = this.GetType();

                Type MyType = this.GetType();                

                PropertyInfo[] MyFields = MyType.GetProperties();
                //поля узла
                foreach (PropertyInfo inf in MyFields)
                {
                    s = inf.Name;
                    if(s.IndexOf("FilterList")>-1)
                    {
                        string d = "";
                    }
                    //Если свойство входит в список не серилизуемых свойств - пропускаем
                    if(this.PropIsNonSerilize (inf.Name)) continue ;


                    //читаем перечисления
                    if(IsEnum (inf))
                    {
                        XmlAttribute vEnum = Node.Attributes[inf.Name];
                        if (vEnum == null) continue;
                        object r = Enum.Parse(inf.PropertyType, vEnum.Value);
                        inf.SetValue(this, r, null);
                        continue;
                    }


                    // если это бизнес объект и для него есть значения в xml - десерилизуем его
                    if (PropertyIsSerializableItem(inf.PropertyType))
                    {
                        XmlNode ChildNode = GetNodeByName(Node, inf.Name);
                        if (!(null == ChildNode))
                        {
                            ISerializableItem I;
                            object PropValue = inf.GetValue(this, null);
                            if (null == PropValue)
                            {
                                // Получаем  тип указанный в ноде - если его не существует - игнорируем заполнение бизнесобъекта
                                Type Tr = MyGetType(ChildNode.Attributes[SerilizeContext.AssemblyPref].Value, ChildNode.Attributes[SerilizeContext.TypePref].Value);

                                if (null == Tr)
                                            continue;

                                

                                    // сформировать бизнес объект и заполнить его 
                                    NewObj    = (Entity)Activator.CreateInstance(Tr, null);
                                    PropValue = NewObj;
                                    inf.SetValue(this, PropValue, null);
                                
                               
                                
                            }
                            I = (ISerializableItem)PropValue;

                            I.ReadXml(ChildNode);

                        }
                        continue;
                    }
                    

                    // выставляем значение атрибута
                    XmlAttribute fValue = Node.Attributes[inf.Name];
                    if (fValue == null) continue;
                    if (string.IsNullOrEmpty(fValue.Value)) continue;
                    
                    object val = null;
                    Type t = inf.PropertyType;                    
                    val = fValue.Value.ToString();                    
                    
                    if (t.Equals(typeof(Decimal))) val = decimal.Parse(fValue.Value);
                    if (t.Equals(typeof(DateTime))) val = DateTime.Parse(fValue.Value);
                    if (t.Equals(typeof(int))) val = int.Parse(fValue.Value);
                    if (t.Equals(typeof(bool))) val = bool.Parse (fValue.Value);

                    if (inf.CanWrite)
                        inf.SetValue(this, val, null);
                }
            }

            catch (Exception ex)
            {
                string s2 = s;
                throw new Exception
                    (string.Format("Не могу серилизовать объект из ноды {0}", Node.InnerXml), ex);
            }
        }

        public virtual void ReadXml(System.Xml.XmlNode Node, string Assembly)
        {
        }

        public virtual void ReadXml(string sNode)
        {
            XmlDocument d = new XmlDocument();
            try
            {
                TextReader stringReader = new StringReader(sNode);
                XmlTextReader r = new XmlTextReader(stringReader);
                d.Load(r);
                if (null == d.FirstChild) return ;
                ReadXml(d.FirstChild);
            }
            finally
            {

            }

        }

        /// <summary>
        /// Пишет объект в виде XML строчки в передаваемый ему Writer
        /// </summary>
        public  virtual  void WriteXml(System.Xml.XmlWriter writer, string ObjectName, bool Full)
        {
        }

        public virtual void WriteShortXml(System.Xml.XmlWriter writer, string ObjectName)
        { 
        }


        private bool IsEnum(PropertyInfo inf)
        {             
            if (null == inf.PropertyType.BaseType)
                return false;
          
            return (inf.PropertyType.BaseType.FullName.IndexOf("System.Enum") > -1);
        }
        
        /// <summary>
        ///  Определяет набор префиксов. Свойства имеющие в своем названиии даный префикс - серелизоваться не будут
        ///  Для базовых классов  свойства с префиксоь Def_  в названии. 
        /// </summary>
        public  virtual ArrayList GetNonSerializablePropPref()
        {
                        
                ArrayList r = new ArrayList(_NonSerializablePropPref);
                return r;
            
        }


        /// <summary>
        ///Вернуть  набор публичных свойств  относящихся к простым (String,Decimal,DateTime,int)
        /// </summary>
        internal ArrayList AttPropertys
        {
            get
            {
                ArrayList result = new ArrayList();
                PropertyInfo[] MyFields = this.GetType().GetProperties();
                foreach (PropertyInfo inf in MyFields)
                {
                    if (!ReflectionItem.ImSerisableAtt(inf)) continue;
                    if (PropIsNonSerilize(inf.ToString())) continue; // исключить из серелизации                     
                    result.Add(inf);
                }
                return result;
            }
        }

        /// <summary>
        /// Набор свойств-объектов поддерживающих ISerilze
        /// </summary>
        protected ArrayList ISerilzeProperties
        {
            get
            {
                return GetPropsSupportInterfce(typeof(ISerializableItem));
            }
        }

        /// <summary>
        /// Набор перечеслений среди свойств
        /// </summary>
        protected ArrayList IEnum
        {
            get
            {
                
                    ArrayList Result = new ArrayList();
                    PropertyInfo[] MyFields = this.GetType().GetProperties();

                    foreach (PropertyInfo inf in MyFields)
                    {

                        if (PropIsNonSerilize(inf.ToString())) 
                            continue; 
                        
                        if (IsEnum (inf))
                            Result.Add(inf);


                    }
                    return Result;
                
            }
        }

        /// <summary>
        /// Набор объектов поддерживающих IEntity и не входящих в список исключений
        /// </summary>
        internal  ArrayList IEntityProperties
        {
            get
            {
                return GetPropsSupportInterfce(typeof(IEntity));
            }
        }

       /// <summary>
       /// Вычислить относитьмся ли свойство к несерилизуемым
       /// </summary>
       /// <param name="PropName"></param>
       /// <returns></returns>
        private bool PropIsNonSerilize(string PropName)
        {


            foreach (string s in GetNonSerializablePropPref())
            {
                if (PropName.ToUpper().IndexOf(s.ToUpper()) > -1) return true;
            }

            return false;
        }
                
        /// <summary>
        /// Вернуть свойства экземпляра поддерживающего заданный интерфейс.
        /// Просматриваются только public свойства. 
        /// </summary>
        /// <param name="VarInterface">Заданный интерфейс </param>
        /// <returns></returns>
        private ArrayList GetPropsSupportInterfce(Type VarInterface)
        {

            ArrayList Result = new ArrayList();
            if (null == VarInterface) return Result;

            PropertyInfo[] MyFields = this.GetType().GetProperties();

            foreach (PropertyInfo inf in MyFields)
            {

                if (PropIsNonSerilize(inf.ToString())) continue; // исключить из серелизации 

                foreach (Type item in inf.PropertyType.GetInterfaces())
                {
                    if (item.Equals(VarInterface)) Result.Add(inf);
                }

            }
            return Result;
        }

        #endregion

        #region IEntity Members


        public virtual void Save()
        {
         

        }

        public virtual void Save(string ConnectionString)
        {


        }

        public virtual  void Load()
        {
              
        }

        /// <summary>
        ///  Проверка объекта xslt преобразованием. 
        ///  Xslt преобразование - должно уметь вормировать структуру пригодную для
        ///  foreach (XmlNode nd in D.FirstChild.ChildNodes) result.Add(nd.Attributes["Mess"].Value);        
        /// </summary>
        /// <param name="XsltFile"></param>
        /// <returns></returns>
        public ArrayList Check(XmlTextReader XsltFile)
        {
            ArrayList result = new ArrayList();

            // Взять бизнес объект как XML
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(this.ToXml(true));


            // создать поток с файлом XSLT            
            XmlTextReader r = XsltFile;

            // загрузить xslt 
            XslCompiledTransform tr = new XslCompiledTransform();
            tr.Load(r);

            //поток для результата
            MemoryStream ms = new MemoryStream();
            XmlTextWriter xw = new XmlTextWriter(ms, Encoding.UTF8);

            //преобразовать XML-XML   
            tr.Transform(doc.CreateNavigator(), xw);
            r.Close();


            // считать результаты проверки
            ms.Position = 0;
            XmlDocument D = new XmlDocument();
            D.Load(ms);
            XmlTextReader r_reader = new XmlTextReader(ms);

            foreach (XmlNode nd in D.FirstChild.ChildNodes)
            {
                result.Add(nd.Attributes["Mess"].Value);
            }
            r_reader.Close();
            return result;

        }

      
     
        /// <summary>
        /// ИД коннекции в пуле коннекций - пользуемой данным объектом для раюоты с БД.
        /// По умолчанию AppContext.AppSCPref
        /// </summary>        
        public   virtual string Def_SCName
        {
            get { return AppContext.AppSCPref; }
        }
        
       
        /// <summary>
        /// Выставляет свойство HashSign = EntityState.Deleted в зависимости от этого свойства для объектов Isql Object - вызывается либо Save, либо Delete
        /// </summary>
        public virtual void DestroyMySelf()
        {
            _HashSign = EntityState.Deleted.ToString();   
        }

       
        #endregion


        /// <summary>
        /// указывает поддерживает ли экземпляр интерфейс ISqlSignature
        /// </summary>
        [EntityNonSerializedAttribute]
        public bool ImSql
        { 
            get {

                Type[] interf= GetType().GetInterfaces();
                return ImSupprotType(interf, typeof(ISqlSignature));     
            }

        }

        /// <summary>
        /// Таg под которым будет серилизован объект
        /// </summary>
        [EntityNonSerialized]        
        protected   virtual string TagName
        {
            get { return ToString(); } 
        }

        /// <summary>
        /// Формирировать читабельный-отчетный XML
        /// </summary>
        /// <param name="writer"></param>        
        public virtual  void WriteReportXml(System.Xml.XmlWriter writer,string ObjectName)
        {

        }

        /// <summary>
        /// Наименование статического типа - содержащего функции расширения  для данного типа
        /// Используется
        /// </summary>
        protected  virtual string ReportTypeExt
        {
            get { return string.Empty;}
        }
    }
        
}
